﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace tablegen2.logic.parser.DataTableGenerator
{
    public delegate void DataTableCodeGenerator(DataTableProcessor dataTableProcessor, StringBuilder codeContent, object userData);

    public sealed partial class DataTableProcessor
    {
        private const string CommentLineSeparator = "#";
        private static readonly char[] DataSplitSeparators = new char[] { '\t' };
        private static readonly char[] DataTrimSeparators = new char[] { '\"' };

        private readonly string[] m_NameRow;
        private readonly string[] m_TypeRow;
        private readonly string[] m_DefaultValueRow;
        private readonly string[] m_CommentRow;
        private readonly int m_ContentStartRow;
        private readonly int m_IdColumn;

        private readonly DataProcessor[] m_DataProcessor;
        private readonly string[][] m_RawValues;
        private readonly string[] m_Strings;

        private string m_CodeTemplate;
        private DataTableCodeGenerator m_CodeGenerator;



        public DataTableProcessor(string dataTableFileName,Encoding encoding,int nameRow ,int typeRow,int? defaultValueRow, int? commentRow, int contentStartRow,int idColumn )
        {
            if (string.IsNullOrEmpty(dataTableFileName))
            {
                throw new Exception("Data table file name is invalid");
            }
            if (!dataTableFileName.EndsWith(".txt", StringComparison.Ordinal))
            {
                throw new Exception($"Data table file {dataTableFileName} is not a txt.");

            }
            if (!File.Exists(dataTableFileName))
            {
                throw new Exception($"Data table file {dataTableFileName} is not exists.");
            }


            string[] lines = File.ReadAllLines(dataTableFileName,encoding);
            int rawRowCount = lines.Length;
            int rawColumnCount = 0;

            List<string[]> rawValues = new List<string[]>();

            for (int i = 0; i < lines.Length; i++)
            {
                string[] rawValue = lines[i].Split(DataSplitSeparators);

                for (int j = 0; j < rawValue.Length; j++)
                {
                    rawValue[i].Trim(DataTrimSeparators);
                }

                if (i == 0)
                {
                    rawRowCount = rawValue.Length;
                }
                else if (rawValue.Length != rawColumnCount)
                {
                    throw new Exception($"Data table file '{dataTableFileName}', raw Column is '{ i }', but line '{rawColumnCount.ToString()}' column is '{ rawValue.Length }'.");
                }
                rawValues.Add(rawValue);

            }

            m_RawValues = rawValues.ToArray();

            if (nameRow < 0)
            {
                throw new Exception($"Name row '{nameRow}' is invalid.");
            }

            if (typeRow < 0)
            {
                throw new Exception($"Type row '{typeRow}' is invalid.");
            }

            if (contentStartRow < 0)
            {
                throw new Exception($"Content start row '{contentStartRow}' is invalid.");
            }

            if (idColumn < 0)
            {
                throw new Exception($"Id column '{idColumn}' is invalid.");
            }

            if (nameRow >= rawRowCount)
            {
                throw new Exception($"Name row '{nameRow}' >= raw row count '{rawRowCount}' is not allow.");
            }

            if (typeRow >= rawRowCount)
            {
                throw new Exception($"Type row '{typeRow}' >= raw row count '{rawRowCount}' is not allow.");
            }

            if (defaultValueRow.HasValue && defaultValueRow.Value >= rawRowCount)
            {
                throw new Exception($"Default value row '{defaultValueRow}' >= raw row count '{rawRowCount}' is not allow.");
            }

            if (commentRow.HasValue && commentRow.Value >= rawRowCount)
            {
                throw new Exception($"Comment row '{commentRow}' >= raw row count '{rawRowCount}' is not allow.");
            }

            if (contentStartRow > rawRowCount)
            {
                throw new Exception($"Content start row '{contentStartRow}' > raw row count '{rawRowCount}' is not allow.");
            }

            if (idColumn >= rawColumnCount)
            {
                throw new Exception($"Id column '{idColumn}' >= raw column count '{rawColumnCount}' is not allow.");
            }

            m_NameRow = m_RawValues[nameRow];
            m_TypeRow = m_RawValues[typeRow];
            m_DefaultValueRow = defaultValueRow.HasValue ? m_RawValues[commentRow.Value] : null;
            m_CommentRow = commentRow.HasValue ? m_RawValues[commentRow.Value] : null;
            m_ContentStartRow = contentStartRow;
            m_IdColumn = idColumn;

            m_DataProcessor = new DataProcessor[rawColumnCount];

            for (int i = 0; i < rawColumnCount; i++)
            {
                if (i == idColumn)
                {
                    m_DataProcessor[i] = DataProcessorUtility.GetDataProcessor("id");
                }
                else 
                {
                    m_DataProcessor[i] = DataProcessorUtility.GetDataProcessor(m_TypeRow[i]);
                }
            }

            IDictionary<string, int> strings = new Dictionary<string, int>(StringComparer.Ordinal);

            for (int i = contentStartRow; i < rawRowCount; i++)
            {
                if (IsCommentRow(i))
                {
                    continue;
                }

                for (int j = 0; j < rawColumnCount; j++)
                {
                    if (m_DataProcessor[j].LanguageKeyword != "string")
                    {
                        continue;
                    }

                    string str = m_RawValues[i][j];
                    if (strings.ContainsKey(str))
                    {
                        strings[str]++;
                    }
                    else
                    {
                        strings[str] = 1;
                    }
                }
            }

            m_Strings = strings.OrderBy(value => value.Key).OrderByDescending(value => value.Value).Select(value => value.Key).ToArray();

            m_CodeTemplate = null;
            m_CodeGenerator = null;

        }

        public int RawRowCount
        {
            get
            {
                return m_RawValues.Length;
            }
        }

        public int RawColumnCount
        {
            get
            {
                return m_RawValues.Length > 0 ? m_RawValues[0].Length : 0;
            }
        }

        public int StringCount
        {
            get
            {
                return m_Strings.Length;
            }
        }

        public int ContentStartRow
        {
            get
            {
                return m_ContentStartRow;
            }
        }

        public int IdColumn
        {
            get
            {
                return m_IdColumn;
            }
        }


        public bool IsIdColumn(int rawColumn)
        {
            if (rawColumn < 0 || rawColumn >= RawColumnCount)
            {
                throw new Exception($"Raw column '{rawColumn}' is out of range.");
            }

            return m_DataProcessor[rawColumn].IsId;
        }

        public bool IsCommentRow(int rawRow)
        {
            if (rawRow < 0 || rawRow >= RawRowCount)
            {
                throw new Exception($"Raw row '{rawRow}' is out of range." );
            }

            return GetValue(rawRow, 0).StartsWith(CommentLineSeparator, StringComparison.Ordinal);
        }

        public bool IsCommentColumn(int rawColumn)
        {
            if (rawColumn < 0 || rawColumn >= RawColumnCount)
            {
                throw new Exception($"Raw column '{rawColumn}' is out of range.");
            }

            return string.IsNullOrEmpty(GetName(rawColumn)) || m_DataProcessor[rawColumn].IsComment;
        }

        public string GetName(int rawColumn)
        {
            if (rawColumn < 0 || rawColumn >= RawColumnCount)
            {
                throw new Exception($"Raw column '{rawColumn}' is out of range.");
            }

            if (IsIdColumn(rawColumn))
            {
                return "Id";
            }

            return m_NameRow[rawColumn];
        }

        public bool IsSystem(int rawColumn)
        {
            if (rawColumn < 0 || rawColumn >= RawColumnCount)
            {
                throw new Exception($"Raw column '{rawColumn}' is out of range.");
            }

            return m_DataProcessor[rawColumn].IsSystem;
        }

        public System.Type GetType(int rawColumn)
        {
            if (rawColumn < 0 || rawColumn >= RawColumnCount)
            {
                throw new Exception($"Raw column '{rawColumn}' is out of range.");
            }

            return m_DataProcessor[rawColumn].Type;
        }

        public string GetLanguageKeyword(int rawColumn)
        {
            if (rawColumn < 0 || rawColumn >= RawColumnCount)
            {
                throw new Exception($"Raw column '{rawColumn}' is out of range.");
            }

            return m_DataProcessor[rawColumn].LanguageKeyword;
        }

        public string GetDefaultValue(int rawColumn)
        {
            if (rawColumn < 0 || rawColumn >= RawColumnCount)
            {
                throw new Exception($"Raw column '{rawColumn}' is out of range.");
            }

            return m_DefaultValueRow != null ? m_DefaultValueRow[rawColumn] : null;
        }

        public string GetComment(int rawColumn)
        {
            if (rawColumn < 0 || rawColumn >= RawColumnCount)
            {
                throw new Exception($"Raw column '{rawColumn}' is out of range.");
            }

            return m_CommentRow != null ? m_CommentRow[rawColumn] : null;
        }

        public string GetValue(int rawRow, int rawColumn)
        {
            if (rawRow < 0 || rawRow >= RawRowCount)
            {
                throw new Exception($"Raw row '{rawRow}' is out of range.");
            }

            if (rawColumn < 0 || rawColumn >= RawColumnCount)
            {
                throw new Exception($"Raw column '{rawColumn}' is out of range.");
            }

            return m_RawValues[rawRow][rawColumn];
        }

        public string GetString(int index)
        {
            if (index < 0 || index >= StringCount)
            {
                throw new Exception($"String index '{index}' is out of range.");
            }

            return m_Strings[index];
        }


    }
}
